package com.nulstudio.cloud.commodity.config;

import jakarta.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

/**
 * Security configuration for this application, using Spring Security.
 * Method security is enabled.
 * @author nullsty
 * @since 2.0
 */
@Configuration
@EnableWebSecurity @EnableMethodSecurity
public class NulSecurityConfig {

    /**
     * Authentication exception handler for security module.
     */
    @Resource @NonNull
    private NulAuthenticationEntryPoint authenticationEntryPoint;

    /**
     * Inject a new {@code NulJwtTokenFilter} for JWT authentication.
     * @return an instance of {@code NulJwtTokenFilter}
     */
    @Bean
    protected NulJwtTokenFilter jwtTokenFilter() {
        return new NulJwtTokenFilter();
    }

    /**
     * Configure Spring Security filter chain.
     * @param http http security configuration
     * @return an instance of {@code SecurityFilterChain}
     * @throws Exception when internal errors in Spring Security occurred
     */
    @Bean
    protected SecurityFilterChain filterChain(@NonNull HttpSecurity http) throws Exception {
        // Configure Spring Security filter chain.
        http
                // Disable CSRF configurations.
                .csrf(AbstractHttpConfigurer::disable)

                // Disable session states.
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))

                // Configure paths to authenticate.
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/account").permitAll()
                        .anyRequest().authenticated()
                )

                // Exception handling logic for security.
                .exceptionHandling(exception -> exception.authenticationEntryPoint(authenticationEntryPoint))

                // CORS Configuration.
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))

                // Add a filter to validate JWT token.
                .addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    /**
     * Get CORS Configuration.
     * @return CORS Configuration
     */
    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
        final CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedOrigins(List.of("*"));
        corsConfiguration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
        corsConfiguration.setAllowedHeaders(List.of("*"));
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

    /**
     * Get ID of current user.
     * @return ID of current user
     */
    @Nullable
    public static int getContextUserId() {
        return (Integer) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }

    /**
     * Get token of the request.
     * @return token of the request
     */
    @Nullable
    public static String getToken() {
        return (String) SecurityContextHolder.getContext().getAuthentication().getCredentials();
    }
}
